package com.pavlinic.mylisp;

public class Function implements Expression {
	private final ListExpression argumentList;
	private final Expression body;

	public Function(ListExpression argumentList, Expression body) {
		this.argumentList = argumentList;
		this.body = body;
	}

	public Expression evaluate(EvaluationContext evaluationContext) {
		return this;
	}

	public ListExpression getArgumentList() {
		return argumentList;
	}

	public Expression getBody() {
		return body;
	}

	@Override
	public String toString() {
		return "(lambda " + argumentList + " " + body + ")";
	}

	public Expression call(ListExpression parameterValues, EvaluationContext evaluationContext) {
		if (ListExpression.TRACE) {
			System.out.println("calling function with parameters: " + parameterValues);
		}
		
		if (!isVariadic() && parameterValues.size() != argumentList.size()) {
			throw new RuntimeException("Invalid number of arguments to : " + this);
		}
		for (int i = 0; i < argumentList.size(); i++) {
			final SymbolExpression argument = (SymbolExpression) argumentList.get(i);
			if (isVariadic(argument)) {
				final ListExpression variadicArgument = new ListExpression(); 
				for (int j = i; j < parameterValues.size(); j++) {
					variadicArgument.add(parameterValues.get(j).evaluate(evaluationContext));
				}
				//Bind all the parameters to the variadic argument, then break out of the loop as
				//we can't bind the rest of the parameters
				evaluationContext = evaluationContext.withBinding(argument.getValue().substring(1), variadicArgument);
				break;
			}
			evaluationContext = evaluationContext.withBinding(argument.getValue(), parameterValues.get(i).evaluate(
					evaluationContext));
		}
		return body.evaluate(evaluationContext);
	}

	private boolean isVariadic() {
		for (int i = 0; i < argumentList.size(); i++) {
			SymbolExpression argument = (SymbolExpression) argumentList.get(i);
			if(isVariadic(argument)) {
				return true;
			}
		}
		return false;
	}

	private boolean isVariadic(SymbolExpression argument) {
		return argument.getValue().startsWith("&");
	}

  public Object getValue() {
    return this;
  }
}
